/*
* This file is a part of the open-eBackup project.
* This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
* If a copy of the MPL was not distributed with this file, You can obtain one at
* http://mozilla.org/MPL/2.0/.
*
* Copyright (c) [2024] Huawei Technologies Co.,Ltd.
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
*/
#include <curl/curl.h>
#include "KubeConfig.h"

KUBERNETES_PLUGIN_NAMESPACE_BEGIN

namespace {
const std::string MODULE_NAME = "KubeConfig";

std::string GetCurrentContextFromConfig(const YAML::Node &kubeConfig)
{
    return kubeConfig["current-context"].as<std::string>();
}

ContextInfo GetContextFromConfig(const std::string &contextName, const YAML::Node &kubeConfig)
{
    YAML::Node contexts = kubeConfig["contexts"];
    for (YAML::const_iterator it = contexts.begin(); it != contexts.end(); it++) {
        const YAML::Node& context = *it;
        if (context["name"].as<std::string>() == contextName) {
            ContextInfo ret;
            ret.clusterName = context["context"]["cluster"].as<std::string>();
            ret.userName = context["context"]["user"].as<std::string>();
            return ret;
        }
    }
    ERRLOG("Failed to find valid ContextInfo for contextName: %s", contextName.c_str());
    return ContextInfo();
}

ClusterInfo GetClusterInfoFromConfig(const std::string &clusterName, const YAML::Node &kubeConfig)
{
    YAML::Node clusters = kubeConfig["clusters"];
    for (YAML::const_iterator it = clusters.begin(); it != clusters.end(); it++) {
        const YAML::Node& cluster = *it;
        if (cluster["name"].as<std::string>() == clusterName) {
            ClusterInfo ret;
            ret.server = cluster["cluster"]["server"].as<std::string>();
            ret.crt = cluster["cluster"]["certificate-authority-data"].as<std::string>();
            if (!Module::CodeConvert::DecodeBase64(ret.crt.length(), ret.crt, ret.crt)) {
                ERRLOG("Failed to decode cluster certificate-authority-data");
                break;
            }
            return ret;
        }
    }
    ERRLOG("Failed to find valid ClusterInfo for clusterName: %s", clusterName.c_str());
    return ClusterInfo();
}

UserInfo GetUserInfoFromConfig(const std::string &userName, const YAML::Node &kubeConfig)
{
    YAML::Node users = kubeConfig["users"];
    for (YAML::const_iterator it = users.begin(); it != users.end(); it++) {
        const YAML::Node& user = *it;
        if (user["name"].as<std::string>() == userName) {
            UserInfo ret;
            ret.crt = user["user"]["client-certificate-data"].as<std::string>();
            if (!Module::CodeConvert::DecodeBase64(ret.crt.length(), ret.crt, ret.crt)) {
                ERRLOG("Failed to decode user client-certificate-data");
                break;
            }
            ret.privateKey = user["user"]["client-key-data"].as<std::string>();
            if (!Module::CodeConvert::DecodeBase64(ret.privateKey.length(), ret.privateKey, ret.privateKey)) {
                ERRLOG("Failed to decode user client-key-data");
                break;
            }
            return ret;
        }
    }
    ERRLOG("Failed to find valid UserInfo for username: %s", userName.c_str());
    return UserInfo();
}
}

std::string KubeConfig::GetClusterCrt() const
{
    return m_clusterCA;
}

std::string KubeConfig::GetUserKey() const
{
    return m_userKey;
}

std::string KubeConfig::GetUserCrt() const
{
    return m_userCrt;
}

std::string KubeConfig::GetClusterServer() const
{
    return m_clusterServer;
}

bool KubeConfig::GetClusterServer(std::string &ip, std::string &port) const
{
    if (m_clusterServer.empty()) {
        ERRLOG("Server address is null");
        return false;
    }

    bool ret = true;
    CURLU *h = curl_url();
    CURLUcode rc = curl_url_set(h, CURLUPART_URL, m_clusterServer.c_str(), 0);
    if (!rc) {
        char *host;
        rc = curl_url_get(h, CURLUPART_HOST, &host, 0);
        if (!rc) {
            ip = host;
            curl_free(host);
        } else {
            ret = false;
        }

        char *p;
        rc = curl_url_get(h, CURLUPART_PORT, &p, 0);
        if (!rc) {
            port = p;
            curl_free(p);
        } else {
            ret = false;
        }
    }

    DBGLOG("Server address: %s:%s", ip.c_str(), port.c_str());
    curl_url_cleanup(h);
    return ret;
}

bool KubeConfig::SetSSLContext(boost::asio::ssl::context &sslCtx) const
{
    try {
        sslCtx.set_verify_mode(boost::asio::ssl::verify_peer);
        sslCtx.add_certificate_authority(boost::asio::buffer(m_clusterCA.data(), m_clusterCA.size()));
        sslCtx.use_certificate(
            boost::asio::buffer(m_userCrt.data(), m_userCrt.size()), boost::asio::ssl::context_base::pem);
        sslCtx.use_private_key(
            boost::asio::buffer(m_userKey.data(), m_userKey.size()), boost::asio::ssl::context_base::pem);
    } catch (std::exception &e) {
        ERRLOG("Failed to load certificates failed, error: %s", WIPE_SENSITIVE(e.what()));
        return false;
    }

    return true;
}

std::pair<bool, KubeConfig> KubeConfig::Create(const std::string &codedConfig)
{
    std::string kubeConfig;
    if (!Module::CodeConvert::DecodeBase64(codedConfig.length(), codedConfig, kubeConfig)) {
        ERRLOG("Failed to decode input");
        return std::make_pair(false, KubeConfig());
    }

    YAML::Node yamlNode;
    try {
        yamlNode = YAML::Load(kubeConfig);
    }
    catch (const YAML::ParserException &e) {
        ERRLOG("Illegal yaml exception %s", e.what());
        return std::make_pair(false, KubeConfig());
    };

    ClusterInfo clusterInfo;
    UserInfo userInfo;
    try {
        std::string currentContext = GetCurrentContextFromConfig(yamlNode);
        ContextInfo contextInfo = GetContextFromConfig(currentContext, yamlNode);
        clusterInfo = GetClusterInfoFromConfig(contextInfo.clusterName, yamlNode);
        userInfo = GetUserInfoFromConfig(contextInfo.userName, yamlNode);
    }
    catch (std::exception &e) {
        ERRLOG("InvalidNode msg: %s", e.what());
        return std::make_pair(false, KubeConfig());
    }

    if (clusterInfo.crt.empty() || clusterInfo.server.empty() || userInfo.crt.empty() || userInfo.privateKey.empty()) {
        return std::make_pair(false, KubeConfig());
    }
    return std::make_pair(true, KubeConfig(clusterInfo, userInfo));
}

KUBERNETES_PLUGIN_NAMESPACE_END